package jpm.position;

import static jpm.common.AssertUtils.ensure;
import static jpm.common.AssertUtils.ensureEqual;
import static jpm.common.AssertUtils.ensureNotNull;

import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import jpm.trade.TradeHelper;
import jpm.trade.TradeHelper.Operation;
import jpm.trade.Trade;

public class Cache {

	public enum UpdateStatus {
		IGNORED, PENDING_CANCEL, SUCCESS
	}

	private static ConcurrentHashMap<Integer, Trade> tradeCache = new ConcurrentHashMap<>();
	// tradeId vs tradeProp

	private static ConcurrentHashMap<PositionKey, PositionValue> positionCache = new ConcurrentHashMap<>();

	private static ConcurrentHashMap<Integer, Trade> pendingCancels = new ConcurrentHashMap<>();

	// tradeId vs tradeProp

	public static synchronized UpdateStatus update(Trade trade) {
		TradeHelper.ensureValid(trade);

		// if (trade not in cache)
		// ...if (new or amend)
		// .....add trade to cache
		// .....update position
		// .....process any pending cancels
		// .....return
		// ...end if
		// ...(event is a cancel but trade not in cache)
		// ...add event to pending cancel
		// ...return
		// end if
		// 
		// (trade exists in cache)
		// ignore trade if older version compared to cache
		//
		// get existing trade from cache
		// reverse position for existing trade
		// add trade to cache
		// if (new or amend)
		// ...update position cache
		// ...process any pending cancels
		// ...return
		// end if
		//
		// (cancel trade)
		// update position but set quantity to 0
		// return
		

		int tradeId = trade.getTradeId();
		if (!tradeCache.containsKey(tradeId)) {
			if ((trade.getOperation() == Operation.NEW)
					|| (trade.getOperation() == Operation.AMEND)) {
				tradeCache.put(tradeId, trade);
				updatePositionCache(trade, false);
				processPendingCancelIfAny(trade);
				return UpdateStatus.SUCCESS;
			}

			// cancel event but trade not in cache
			// add to pending cancels
			pendingCancels.put(tradeId, trade);
			return UpdateStatus.PENDING_CANCEL;
		}

		// trade is present in cache
		Trade existing = tradeCache.get(tradeId);

		if (existing.getTradeVersion() > trade.getTradeVersion())
			return UpdateStatus.IGNORED;

		updatePositionCache(existing, true); // reverse existing trade
		tradeCache.put(tradeId, trade);
		if ((trade.getOperation() == Operation.NEW)
				|| (trade.getOperation() == Operation.AMEND)) {

			updatePositionCache(trade, false);
			processPendingCancelIfAny(trade);
			return UpdateStatus.SUCCESS;
		}

		// cancel
		TradeHelper.ensureSameTradeDetails(existing, trade);
		ensureEqual(0, trade.getQuantity(), "Cancel trade [" + tradeId
				+ "] has non zero quantity [" + trade.getQuantity() + "]");
		updatePositionCache(trade, false);
		return UpdateStatus.SUCCESS;
	}

	static synchronized void processPendingCancelIfAny(Trade trade) {
		TradeHelper.ensureValid(trade);

		// return if trade is a cancel
		if (trade.getOperation() == Operation.CANCEL)
			return;

		int tradeId = trade.getTradeId();
		if (pendingCancels.containsKey(tradeId)) {
			Trade pendingCancel = pendingCancels.get(tradeId);
			ensure(Operation.CANCEL == pendingCancel.getOperation(),
					"Invalid operation [" + pendingCancel.getOperation()
							+ "] for a pending cancel trade");

			update(pendingCancel);
			pendingCancels.remove(tradeId);
		}
	}

	static synchronized void updatePositionCache(Trade trade, boolean reverse) {
		ensureNotNull(trade, "trade is null");

		PositionKey key = new PositionKey(trade.getSecurity(),
				trade.getAccountNo());
		if (!positionCache.containsKey(key))
			positionCache.put(key, new PositionValue());

		PositionValue value = positionCache.get(key);
		ensureNotNull(value, "position value is null");

		int quantity = trade.getQuantity();
		int tradeId = trade.getTradeId();
		if (trade.isBuy()) {
			if (!reverse) {
				value.addQuantity(quantity);
				value.addTradeId(tradeId);
			} else {
				value.addQuantity(-quantity);
				value.removeTradeId(tradeId);
			}
		} else {
			if (!reverse) {
				value.addQuantity(-quantity);
				value.addTradeId(tradeId);
			} else {
				value.addQuantity(quantity);
				value.removeTradeId(tradeId);
			}
		}
	}

	public static Map<PositionKey, PositionValue> getPositionCache() {
		ensureNotNull(positionCache, "position cache is null");

		return Collections.unmodifiableMap(positionCache);
		// both PositionKeyProp and PositionValueProp are immutable outside the
		// package
	}

	public static Map<Integer, Trade> getPendingCancels() {
		ensureNotNull(pendingCancels, "pendingCancels is null");

		return Collections.unmodifiableMap(pendingCancels);
		// TradeProp is immutable
	}

	static Map<Integer, Trade> getTradeCache() {
		ensureNotNull(tradeCache, "tradeCache is null");

		return Collections.unmodifiableMap(tradeCache);
	}

	static synchronized void clearCache() {
		// used for testing
		tradeCache.clear();
		positionCache.clear();
		pendingCancels.clear();
	}
}
